---
title: Functions and Scope Mechanisms
description: Deep understanding of function definition, calling mechanisms, scope rules, and closure concepts in Python and JavaScript, mastering higher-order functions and functional programming patterns.
---

# Functions and Scope Mechanisms

Functions are core building blocks of programming. Both Python and JavaScript support functional programming, but they differ significantly in function definition, scope rules, and closure behavior. This module will help you understand these differences and master effective function usage in JavaScript.

## Function Definition and Calling

### Basic Function Definition

<PythonEditor title="Function Definition Methods" compare={true}>
```python !! py
# Python function definition

# 1. Basic function definition
def greet(name):
    """Greeting function"""
    return f"Hello, {name}!"

# 2. Function with default parameters
def greet_with_title(name, title="Mr./Ms."):
    """Greeting function with default parameters"""
    return f"Hello, {title} {name}!"

# 3. Variable arguments function
def sum_numbers(*args):
    """Calculate sum of variable number of arguments"""
    return sum(args)

def print_info(**kwargs):
    """Print keyword argument information"""
    for key, value in kwargs.items():
        print(f"{key}: {value}")

# 4. Mixed parameters function
def complex_function(required, default="default", *args, **kwargs):
    """Complex parameter function"""
    print(f"Required: {required}")
    print(f"Default: {default}")
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

# Function call examples
print(greet("Alice"))
print(greet_with_title("Bob", "Dr."))
print(sum_numbers(1, 2, 3, 4, 5))

print_info(name="Charlie", age=30, city="New York")

complex_function("test", "custom", 1, 2, 3, extra="value")

# 5. Lambda functions
square = lambda x: x ** 2
add = lambda x, y: x + y

print(f"Square of 5: {square(5)}")
print(f"Add 3 and 4: {add(3, 4)}")

# 6. Functions as first-class objects
def multiply(x, y):
    return x * y

# Function assignment
operation = multiply
result = operation(4, 5)
print(f"Operation result: {result}")

# Function as parameter
def apply_operation(a, b, func):
    return func(a, b)

result = apply_operation(6, 7, multiply)
print(f"Apply operation: {result}")
```

```javascript !! js
// JavaScript function definition

// 1. Function declaration
function greet(name) {
    // Function body
    return `Hello, ${name}!`;
}

// 2. Function expression
const greetExpression = function(name) {
    return `Hello, ${name}!`;
};

// 3. Arrow function (ES6+)
const greetArrow = (name) => {
    return `Hello, ${name}!`;
};

// Simplified arrow function
const greetSimple = name => `Hello, ${name}!`;

// 4. Function with default parameters (ES6+)
function greetWithTitle(name, title = "Mr./Ms.") {
    return `Hello, ${title} ${name}!`;
}

// 5. Variable arguments function (Rest parameters)
function sumNumbers(...args) {
    return args.reduce((sum, num) => sum + num, 0);
}

function printInfo(requiredParam, ...otherArgs) {
    console.log(`Required: ${requiredParam}`);
    console.log(`Other args: ${otherArgs}`);
}

// 6. Object destructuring parameters
function printUserInfo({name, age, city = "Unknown"}) {
    console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}

// Function call examples
console.log(greet("Alice"));
console.log(greetWithTitle("Bob", "Dr."));
console.log(sumNumbers(1, 2, 3, 4, 5));

printInfo("required", "arg1", "arg2", "arg3");
printUserInfo({name: "Charlie", age: 30, city: "New York"});

// 7. Immediately Invoked Function Expression (IIFE)
const result = (function(x, y) {
    return x + y;
})(5, 3);
console.log(`IIFE result: ${result}`);

// 8. Functions as first-class objects
function multiply(x, y) {
    return x * y;
}

// Function assignment
const operation = multiply;
const result2 = operation(4, 5);
console.log(`Operation result: ${result2}`);

// Function as parameter
function applyOperation(a, b, func) {
    return func(a, b);
}

const result3 = applyOperation(6, 7, multiply);
console.log(`Apply operation: ${result3}`);

// 9. Method definition (in objects)
const calculator = {
    add(a, b) {
        return a + b;
    },
    
    subtract: function(a, b) {
        return a - b;
    },
    
    multiply: (a, b) => a * b  // Note: arrow functions don't have their own 'this'
};

console.log(`Calculator add: ${calculator.add(10, 5)}`);
console.log(`Calculator subtract: ${calculator.subtract(10, 5)}`);
console.log(`Calculator multiply: ${calculator.multiply(10, 5)}`);
```
</PythonEditor>

### Parameter Handling

<PythonEditor title="Parameter Handling Mechanisms" compare={true}>
```python !! py
# Python parameter handling

# 1. Positional and keyword arguments
def process_data(data, format_type="json", verbose=False, **options):
    """Data processing function"""
    print(f"Data: {data}")
    print(f"Format: {format_type}")
    print(f"Verbose: {verbose}")
    if options:
        print(f"Options: {options}")

# Different calling methods
process_data("sample_data")
process_data("sample_data", "xml")
process_data("sample_data", verbose=True)
process_data("sample_data", format_type="csv", verbose=True, encoding="utf-8")

# 2. Keyword-only arguments (Python 3+)
def create_user(name, *, age, email):
    """Create user, age and email must be keyword arguments"""
    return {
        "name": name,
        "age": age,
        "email": email
    }

# Must use keyword arguments
user = create_user("Alice", age=25, email="alice@example.com")
print(f"Created user: {user}")

# 3. Argument unpacking
def add_three_numbers(a, b, c):
    return a + b + c

numbers = [1, 2, 3]
result = add_three_numbers(*numbers)  # Unpack list
print(f"Sum with unpacking: {result}")

# Dictionary unpacking
def introduce(name, age, city):
    return f"I'm {name}, {age} years old, from {city}"

person_info = {"name": "Bob", "age": 30, "city": "London"}
introduction = introduce(**person_info)  # Unpack dictionary
print(introduction)

# 4. Type annotations (Type hints)
from typing import List, Dict, Optional, Union, Callable

def calculate_average(numbers: List[float]) -> float:
    """Calculate average"""
    if not numbers:
        return 0.0
    return sum(numbers) / len(numbers)

def process_callback(data: List[int], callback: Callable[[int], int]) -> List[int]:
    """Process data and apply callback function"""
    return [callback(item) for item in data]

# Using type-annotated functions
average = calculate_average([1.5, 2.5, 3.5])
print(f"Average: {average}")

processed = process_callback([1, 2, 3, 4], lambda x: x * 2)
print(f"Processed: {processed}")

# 5. Function overloading (using functools.singledispatch)
from functools import singledispatch

@singledispatch
def process_value(value):
    """Default processing function"""
    return f"Unknown type: {type(value)}"

@process_value.register
def _(value: int):
    return f"Integer: {value * 2}"

@process_value.register
def _(value: str):
    return f"String: {value.upper()}"

@process_value.register
def _(value: list):
    return f"List length: {len(value)}"

# Test function overloading
print(process_value(42))
print(process_value("hello"))
print(process_value([1, 2, 3]))
print(process_value(3.14))
```

```javascript !! js
// JavaScript parameter handling

// 1. Default parameters and destructuring
function processData(data, {
    formatType = "json",
    verbose = false,
    encoding = "utf-8",
    ...options
} = {}) {
    console.log(`Data: ${data}`);
    console.log(`Format: ${formatType}`);
    console.log(`Verbose: ${verbose}`);
    console.log(`Encoding: ${encoding}`);
    if (Object.keys(options).length > 0) {
        console.log(`Options: ${JSON.stringify(options)}`);
    }
}

// Different calling methods
processData("sample_data");
processData("sample_data", {formatType: "xml"});
processData("sample_data", {verbose: true});
processData("sample_data", {
    formatType: "csv",
    verbose: true,
    compression: "gzip"
});

// 2. Parameter validation and type checking
function createUser(name, {age, email, ...options} = {}) {
    // Parameter validation
    if (typeof name !== 'string' || !name.trim()) {
        throw new Error("Name must be a non-empty string");
    }
    
    if (typeof age !== 'number' || age < 0) {
        throw new Error("Age must be a non-negative number");
    }
    
    if (typeof email !== 'string' || !email.includes('@')) {
        throw new Error("Email must be a valid string");
    }
    
    return {
        name,
        age,
        email,
        ...options
    };
}

// Using object parameters to simulate keyword arguments
const user = createUser("Alice", {
    age: 25,
    email: "alice@example.com",
    city: "New York"
});
console.log(`Created user: ${JSON.stringify(user)}`);

// 3. Spread operator and rest parameters
function addThreeNumbers(a, b, c) {
    return a + b + c;
}

const numbers = [1, 2, 3];
const result = addThreeNumbers(...numbers);  // Spread array
console.log(`Sum with spread: ${result}`);

// Object spread
function introduce({name, age, city, ...otherInfo}) {
    const intro = `I'm ${name}, ${age} years old, from ${city}`;
    if (Object.keys(otherInfo).length > 0) {
        return `${intro}. Additional info: ${JSON.stringify(otherInfo)}`;
    }
    return intro;
}

const personInfo = {
    name: "Bob",
    age: 30,
    city: "London",
    profession: "Developer",
    hobby: "Reading"
};
const introduction = introduce(personInfo);
console.log(introduction);

// 4. Function overloading simulation
function processValue(value) {
    // Dispatch based on type
    switch (typeof value) {
        case 'number':
            return `Number: ${value * 2}`;
        case 'string':
            return `String: ${value.toUpperCase()}`;
        case 'object':
            if (Array.isArray(value)) {
                return `Array length: ${value.length}`;
            } else if (value === null) {
                return "Null value";
            } else {
                return `Object keys: ${Object.keys(value).length}`;
            }
        case 'boolean':
            return `Boolean: ${!value}`;
        default:
            return `Unknown type: ${typeof value}`;
    }
}

// Test function overloading
console.log(processValue(42));
console.log(processValue("hello"));
console.log(processValue([1, 2, 3]));
console.log(processValue({a: 1, b: 2}));
console.log(processValue(true));

// 5. Advanced parameter handling
class AdvancedFunctions {
    // Method overloading simulation
    static calculate(...args) {
        if (args.length === 1) {
            return args[0] ** 2;  // Square
        } else if (args.length === 2) {
            return args[0] + args[1];  // Addition
        } else if (args.length === 3) {
            return args[0] * args[1] * args[2];  // Multiplication
        } else {
            throw new Error("Unsupported number of arguments");
        }
    }
    
    // Currying function
    static curry(fn) {
        return function curried(...args) {
            if (args.length >= fn.length) {
                return fn.apply(this, args);
            } else {
                return function(...args2) {
                    return curried.apply(this, args.concat(args2));
                };
            }
        };
    }
}

// Test advanced features
console.log(AdvancedFunctions.calculate(5));        // 25
console.log(AdvancedFunctions.calculate(3, 4));     // 7
console.log(AdvancedFunctions.calculate(2, 3, 4));  // 24

// Currying example
const multiply = (a, b, c) => a * b * c;
const curriedMultiply = AdvancedFunctions.curry(multiply);
const multiplyBy2 = curriedMultiply(2);
const multiplyBy2And3 = multiplyBy2(3);
console.log(multiplyBy2And3(4));  // 24
```
</PythonEditor>

## Scope and Variable Lookup

### Scope Rules Comparison

<PythonEditor title="Scope Mechanisms" compare={true}>
```python !! py
# Python scope rules (LEGB)

# Global variable
global_var = "I'm global"

def outer_function():
    # Enclosing scope
    outer_var = "I'm in outer function"
    
    def inner_function():
        # Local scope
        local_var = "I'm local"
        
        # Access variables from different scopes
        print(f"Local: {local_var}")
        print(f"Outer: {outer_var}")
        print(f"Global: {global_var}")
        
        # Built-in scope
        print(f"Built-in len function: {len}")
    
    inner_function()
    return inner_function

# Test scope
outer_function()

# 1. global keyword
counter = 0

def increment_global():
    global counter
    counter += 1
    print(f"Global counter: {counter}")

increment_global()
increment_global()

# 2. nonlocal keyword
def make_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment

counter_func = make_counter()
print(f"Counter: {counter_func()}")  # 1
print(f"Counter: {counter_func()}")  # 2
print(f"Counter: {counter_func()}")  # 3

# 3. Scope and variable shadowing
name = "Global Alice"

def test_shadowing():
    name = "Local Bob"  # Shadows global variable
    print(f"Inside function: {name}")

test_shadowing()
print(f"Outside function: {name}")

# 4. List comprehension scope (Python 3+)
x = "global x"
squares = [x**2 for x in range(5)]  # x is local in comprehension
print(f"After comprehension: {x}")  # Still "global x"

# 5. Scope capture in function definition
functions = []
for i in range(3):
    # Use default parameter to capture current value
    functions.append(lambda x=i: x)

for func in functions:
    print(func())  # 0, 1, 2

# 6. Variable lifetime
def create_closure():
    data = [1, 2, 3, 4, 5]
    
    def get_data():
        return data  # data stays alive in closure
    
    return get_data

getter = create_closure()
print(f"Closure data: {getter()}")

# 7. Module-level scope
# __name__ is a module-level built-in variable
if __name__ == "__main__":
    print("This is the main module")

# Import scope
import math
from datetime import datetime

print(f"Math pi: {math.pi}")
print(f"Current time: {datetime.now()}")
```

```javascript !! js
// JavaScript scope rules

// Global scope
var globalVar = "I'm global";
let globalLet = "I'm also global";
const globalConst = "I'm global too";

function outerFunction() {
    // Function scope
    var outerVar = "I'm in outer function";
    let outerLet = "I'm also in outer";
    
    function innerFunction() {
        // Inner function scope
        var localVar = "I'm local";
        let localLet = "I'm also local";
        
        // Access variables from different scopes
        console.log(`Local var: ${localVar}`);
        console.log(`Local let: ${localLet}`);
        console.log(`Outer var: ${outerVar}`);
        console.log(`Outer let: ${outerLet}`);
        console.log(`Global var: ${globalVar}`);
        console.log(`Global let: ${globalLet}`);
    }
    
    innerFunction();
    return innerFunction;
}

// Test scope
outerFunction();

// 1. var vs let vs const scope differences
function scopeTest() {
    console.log("\n=== Scope Test ===");
    
    if (true) {
        var varVariable = "var in block";
        let letVariable = "let in block";
        const constVariable = "const in block";
    }
    
    console.log(`var outside block: ${varVariable}`);  // Accessible
    // console.log(letVariable);  // ReferenceError
    // console.log(constVariable);  // ReferenceError
}

scopeTest();

// 2. Variable hoisting
function hoistingTest() {
    console.log("\n=== Hoisting Test ===");
    
    console.log(`var before declaration: ${typeof hoistedVar}`);  // undefined
    // console.log(hoistedLet);  // ReferenceError (temporal dead zone)
    
    var hoistedVar = "I'm hoisted";
    let hoistedLet = "I'm not hoisted";
    
    console.log(`var after declaration: ${hoistedVar}`);
    console.log(`let after declaration: ${hoistedLet}`);
}

hoistingTest();

// 3. Closures and variable capture
let counter = 0;

function incrementGlobal() {
    counter++;
    console.log(`Global counter: ${counter}`);
}

incrementGlobal();
incrementGlobal();

// Create closure counter
function makeCounter() {
    let count = 0;
    
    return function() {
        count++;
        return count;
    };
}

const counterFunc = makeCounter();
console.log(`Counter: ${counterFunc()}`);  // 1
console.log(`Counter: ${counterFunc()}`);  // 2
console.log(`Counter: ${counterFunc()}`);  // 3

// 4. Variable shadowing
const name = "Global Alice";

function testShadowing() {
    const name = "Local Bob";  // Shadows outer variable
    console.log(`Inside function: ${name}`);
}

testShadowing();
console.log(`Outside function: ${name}`);

// 5. Closure issues in loops
console.log("\n=== Loop Closure Issues ===");

// Problem: using var
const functionsVar = [];
for (var i = 0; i < 3; i++) {
    functionsVar.push(function() {
        return i;  // All functions reference the same i
    });
}

console.log("Using var:");
functionsVar.forEach(func => console.log(func()));  // 3, 3, 3

// Solution 1: using let
const functionsLet = [];
for (let i = 0; i < 3; i++) {
    functionsLet.push(function() {
        return i;  // Each i is independent
    });
}

console.log("Using let:");
functionsLet.forEach(func => console.log(func()));  // 0, 1, 2

// Solution 2: using IIFE
const functionsIIFE = [];
for (var i = 0; i < 3; i++) {
    functionsIIFE.push((function(index) {
        return function() {
            return index;
        };
    })(i));
}

console.log("Using IIFE:");
functionsIIFE.forEach(func => console.log(func()));  // 0, 1, 2

// 6. Module scope (ES6 modules)
// In actual modules, top-level declarations don't pollute global scope
const moduleVariable = "I'm module-scoped";

// 7. 'this' keyword scope
const obj = {
    name: "Object",
    
    regularMethod: function() {
        console.log(`Regular method this: ${this.name}`);
        
        const innerFunction = function() {
            console.log(`Inner function this: ${this.name}`);  // undefined
        };
        innerFunction();
        
        const arrowFunction = () => {
            console.log(`Arrow function this: ${this.name}`);  // "Object"
        };
        arrowFunction();
    },
    
    arrowMethod: () => {
        console.log(`Arrow method this: ${this.name}`);  // undefined
    }
};

console.log("\n=== This Binding ===");
obj.regularMethod();
obj.arrowMethod();

// 8. Strict mode scope
function strictModeTest() {
    'use strict';
    
    // In strict mode, this doesn't automatically bind to global object
    function innerFunc() {
        console.log(`Strict mode this: ${this}`);  // undefined
    }
    
    innerFunc();
}

strictModeTest();
```
</PythonEditor>

## Closures and Higher-Order Functions

### Closure Mechanisms

<PythonEditor title="Closure Mechanisms" compare={true}>
```python !! py
# Python closure mechanisms

# 1. Basic closure
def create_multiplier(factor):
    """Create a multiplier closure"""
    def multiplier(number):
        return number * factor  # factor is captured by closure
    return multiplier

# Using closures
double = create_multiplier(2)
triple = create_multiplier(3)

print(f"Double 5: {double(5)}")  # 10
print(f"Triple 4: {triple(4)}")  # 12

# 2. Closure modifying external variables
def create_accumulator(initial=0):
    """Create accumulator closure"""
    total = [initial]  # Use list to avoid nonlocal
    
    def accumulate(value):
        total[0] += value
        return total[0]
    
    def get_total():
        return total[0]
    
    def reset():
        total[0] = initial
    
    # Return function object with attached methods
    accumulate.get_total = get_total
    accumulate.reset = reset
    
    return accumulate

acc = create_accumulator(10)
print(f"Accumulate 5: {acc(5)}")    # 15
print(f"Accumulate 3: {acc(3)}")    # 18
print(f"Current total: {acc.get_total()}")  # 18
acc.reset()
print(f"After reset: {acc.get_total()}")    # 10

# 3. Closure using nonlocal
def create_counter(start=0, step=1):
    """Create counter closure"""
    count = start
    
    def increment():
        nonlocal count
        count += step
        return count
    
    def decrement():
        nonlocal count
        count -= step
        return count
    
    def get_value():
        return count
    
    # Return dictionary containing multiple functions
    return {
        'increment': increment,
        'decrement': decrement,
        'get_value': get_value
    }

counter = create_counter(0, 2)
print(f"Initial: {counter['get_value']()}")      # 0
print(f"Increment: {counter['increment']()}")    # 2
print(f"Increment: {counter['increment']()}")    # 4
print(f"Decrement: {counter['decrement']()}")    # 2

# 4. Closure capturing loop variables (common pitfall)
def create_functions_wrong():
    """Wrong way to create closures"""
    functions = []
    for i in range(3):
        functions.append(lambda: i)  # All lambdas share the same i
    return functions

def create_functions_correct():
    """Correct way to create closures"""
    functions = []
    for i in range(3):
        functions.append(lambda x=i: x)  # Use default parameter to capture current i
    return functions

print("\nWrong way:")
wrong_funcs = create_functions_wrong()
for func in wrong_funcs:
    print(func())  # 2, 2, 2

print("\nCorrect way:")
correct_funcs = create_functions_correct()
for func in correct_funcs:
    print(func())  # 0, 1, 2

# 5. Closures in decorators
def timer_decorator(func):
    """Timer decorator"""
    import time
    
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
        return result
    
    return wrapper

@timer_decorator
def slow_function():
    """Simulate slow function"""
    import time
    time.sleep(0.1)
    return "Done!"

result = slow_function()
print(f"Result: {result}")

# 6. Factory functions and configuration closures
def create_validator(min_length=0, max_length=float('inf'), pattern=None):
    """Create validator closure"""
    import re
    
    compiled_pattern = re.compile(pattern) if pattern else None
    
    def validate(value):
        if not isinstance(value, str):
            return False, "Value must be a string"
        
        if len(value) < min_length:
            return False, f"Value too short (min: {min_length})"
        
        if len(value) > max_length:
            return False, f"Value too long (max: {max_length})"
        
        if compiled_pattern and not compiled_pattern.match(value):
            return False, f"Value doesn't match pattern: {pattern}"
        
        return True, "Valid"
    
    return validate

# Create different validators
email_validator = create_validator(5, 100, r'^[^@]+@[^@]+\.[^@]+$')
password_validator = create_validator(8, 50, r'^(?=.*[A-Za-z])(?=.*\d)')

# Test validators
test_email = "user@example.com"
test_password = "mypass123"

email_valid, email_msg = email_validator(test_email)
password_valid, password_msg = password_validator(test_password)

print(f"Email '{test_email}': {email_valid} - {email_msg}")
print(f"Password '{test_password}': {password_valid} - {password_msg}")
```

```javascript !! js
// JavaScript closure mechanisms

// 1. Basic closure
function createMultiplier(factor) {
    // factor is captured by closure
    return function(number) {
        return number * factor;
    };
}

// Simplified arrow function version
const createMultiplierArrow = factor => number => number * factor;

// Using closures
const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(`Double 5: ${double(5)}`);  // 10
console.log(`Triple 4: ${triple(4)}`);  // 12

// 2. Closure modifying external variables
function createAccumulator(initial = 0) {
    let total = initial;
    
    function accumulate(value) {
        total += value;
        return total;
    }
    
    // Add additional methods to function
    accumulate.getTotal = () => total;
    accumulate.reset = () => {
        total = initial;
    };
    
    return accumulate;
}

const acc = createAccumulator(10);
console.log(`Accumulate 5: ${acc(5)}`);      // 15
console.log(`Accumulate 3: ${acc(3)}`);      // 18
console.log(`Current total: ${acc.getTotal()}`);  // 18
acc.reset();
console.log(`After reset: ${acc.getTotal()}`);    // 10

// 3. Returning object with multiple closure functions
function createCounter(start = 0, step = 1) {
    let count = start;
    
    return {
        increment() {
            count += step;
            return count;
        },
        
        decrement() {
            count -= step;
            return count;
        },
        
        getValue() {
            return count;
        },
        
        reset() {
            count = start;
        }
    };
}

const counter = createCounter(0, 2);
console.log(`Initial: ${counter.getValue()}`);      // 0
console.log(`Increment: ${counter.increment()}`);   // 2
console.log(`Increment: ${counter.increment()}`);   // 4
console.log(`Decrement: ${counter.decrement()}`);   // 2

// 4. Closure with loop variables (common pitfalls and solutions)
console.log("\n=== Loop Variable Closure ===");

// Problem: using var (late binding)
function createFunctionsWrongVar() {
    const functions = [];
    for (var i = 0; i < 3; i++) {
        functions.push(function() {
            return i;  // All functions share the same i
        });
    }
    return functions;
}

// Solution 1: using let (block scope)
function createFunctionsWithLet() {
    const functions = [];
    for (let i = 0; i < 3; i++) {
        functions.push(function() {
            return i;  // Each i is independent
        });
    }
    return functions;
}

// Solution 2: using IIFE
function createFunctionsWithIIFE() {
    const functions = [];
    for (var i = 0; i < 3; i++) {
        functions.push((function(index) {
            return function() {
                return index;
            };
        })(i));
    }
    return functions;
}

// Solution 3: using bind
function createFunctionsWithBind() {
    const functions = [];
    function returnValue(value) {
        return value;
    }
    
    for (var i = 0; i < 3; i++) {
        functions.push(returnValue.bind(null, i));
    }
    return functions;
}

// Test different solutions
console.log("Using var (wrong):");
createFunctionsWrongVar().forEach(func => console.log(func()));  // 3, 3, 3

console.log("Using let:");
createFunctionsWithLet().forEach(func => console.log(func()));   // 0, 1, 2

console.log("Using IIFE:");
createFunctionsWithIIFE().forEach(func => console.log(func()));  // 0, 1, 2

console.log("Using bind:");
createFunctionsWithBind().forEach(func => console.log(func()));  // 0, 1, 2

// 5. Module pattern
const MyModule = (function() {
    // Private variables and functions
    let privateVariable = 0;
    const privateArray = [];
    
    function privateFunction(value) {
        return value * 2;
    }
    
    // Public API
    return {
        // Public methods
        addItem(item) {
            privateArray.push(item);
            privateVariable++;
        },
        
        getCount() {
            return privateVariable;
        },
        
        getItems() {
            return [...privateArray];  // Return copy
        },
        
        processItem(item) {
            return privateFunction(item);
        },
        
        clear() {
            privateArray.length = 0;
            privateVariable = 0;
        }
    };
})();

// Using module
MyModule.addItem("item1");
MyModule.addItem("item2");
console.log(`Module count: ${MyModule.getCount()}`);  // 2
console.log(`Module items: ${MyModule.getItems()}`);  // ["item1", "item2"]
console.log(`Process 5: ${MyModule.processItem(5)}`); // 10

// 6. Advanced closures: currying and partial application
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            };
        }
    };
}

// Original function
function add(a, b, c) {
    return a + b + c;
}

// Curried version
const curriedAdd = curry(add);

// Different usage patterns
console.log(curriedAdd(1)(2)(3));     // 6
console.log(curriedAdd(1, 2)(3));     // 6
console.log(curriedAdd(1)(2, 3));     // 6

// Create specialized functions
const add10 = curriedAdd(10);
const add10And5 = add10(5);
console.log(add10And5(3));  // 18

// 7. Memoization closure
function memoize(fn) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            console.log(`Cache hit for ${key}`);
            return cache.get(key);
        }
        
        console.log(`Computing for ${key}`);
        const result = fn.apply(this, args);
        cache.set(key, result);
        
        return result;
    };
}

// Fibonacci sequence (recursive version)
const fibonacci = memoize(function(n) {
    if (n < 2) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(`Fibonacci(10): ${fibonacci(10)}`);
console.log(`Fibonacci(10) again: ${fibonacci(10)}`);  // From cache
```
</PythonEditor>

## Higher-Order Functions and Functional Programming

### Higher-Order Function Patterns

<PythonEditor title="Higher-Order Functions" compare={true}>
```python !! py
# Python higher-order functions and functional programming

# 1. Built-in higher-order functions
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# map: apply function to each element
squared = list(map(lambda x: x**2, numbers))
print(f"Squared: {squared}")

# filter: filter elements
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(f"Evens: {evens}")

# reduce: accumulation operation
from functools import reduce
sum_all = reduce(lambda acc, x: acc + x, numbers, 0)
product_all = reduce(lambda acc, x: acc * x, numbers, 1)
print(f"Sum: {sum_all}, Product: {product_all}")

# 2. Custom higher-order functions
def apply_twice(func, value):
    """Apply function twice"""
    return func(func(value))

def add_one(x):
    return x + 1

result = apply_twice(add_one, 5)  # ((5 + 1) + 1) = 7
print(f"Apply twice: {result}")

# 3. Function composition
def compose(f, g):
    """Function composition: f(g(x))"""
    return lambda x: f(g(x))

def multiply_by_2(x):
    return x * 2

def subtract_1(x):
    return x - 1

# Compose functions: multiply by 2 first, then subtract 1
composed = compose(subtract_1, multiply_by_2)
result = composed(5)  # (5 * 2) - 1 = 9
print(f"Composed function: {result}")

# 4. Multi-function composition
def pipe(*functions):
    """Pipeline operation: apply functions from left to right"""
    return lambda x: reduce(lambda acc, func: func(acc), functions, x)

# Create processing pipeline
process = pipe(
    lambda x: x * 2,      # multiply by 2
    lambda x: x + 10,     # add 10
    lambda x: x ** 2      # square
)

result = process(3)  # ((3 * 2) + 10) ** 2 = 16 ** 2 = 256
print(f"Pipeline result: {result}")

# 5. Decorators as higher-order functions
def log_calls(func):
    """Log function calls decorator"""
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

def retry(max_attempts=3):
    """Retry decorator factory"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"Attempt {attempt + 1} failed: {e}")
            
        return wrapper
    return decorator

@log_calls
@retry(max_attempts=2)
def risky_operation(x, y):
    """Risky operation that might fail"""
    if x < 0:
        raise ValueError("x cannot be negative")
    return x / y

try:
    result = risky_operation(10, 2)
    print(f"Risky operation result: {result}")
except Exception as e:
    print(f"Operation failed: {e}")

# 6. Currying implementation
def curry(func):
    """Curry function"""
    def curried(*args, **kwargs):
        if len(args) + len(kwargs) >= func.__code__.co_argcount:
            return func(*args, **kwargs)
        return lambda *more_args, **more_kwargs: curried(*(args + more_args), **{**kwargs, **more_kwargs})
    return curried

@curry
def add_three(a, b, c):
    return a + b + c

# Using curried function
add_1_2 = add_three(1, 2)
result = add_1_2(3)  # 6
print(f"Curried function: {result}")

# 7. Partial function application
from functools import partial

def multiply(x, y, z):
    return x * y * z

# Create partial function
double = partial(multiply, 2)      # Fix first parameter to 2
result = double(3, 4)              # 2 * 3 * 4 = 24
print(f"Partial function: {result}")

# 8. Functional data processing
def process_data(data):
    """Functional data processing pipeline"""
    return pipe(
        lambda items: filter(lambda x: x > 0, items),           # filter positive numbers
        lambda items: map(lambda x: x * 2, items),              # multiply by 2
        lambda items: filter(lambda x: x % 4 == 0, items),      # filter divisible by 4
        list                                                    # convert to list
    )(data)

test_data = [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8]
processed = process_data(test_data)
print(f"Processed data: {processed}")

# 9. Recursive higher-order functions
def fold_right(func, initial, iterable):
    """Right fold operation"""
    items = list(iterable)
    if not items:
        return initial
    return func(items[0], fold_right(func, initial, items[1:]))

def fold_left(func, initial, iterable):
    """Left fold operation"""
    result = initial
    for item in iterable:
        result = func(result, item)
    return result

# Test fold operations
numbers = [1, 2, 3, 4, 5]
sum_right = fold_right(lambda x, acc: x + acc, 0, numbers)
sum_left = fold_left(lambda acc, x: acc + x, 0, numbers)
print(f"Right fold sum: {sum_right}")
print(f"Left fold sum: {sum_left}")

# 10. Generators and higher-order functions
def generate_fibonacci():
    """Fibonacci sequence generator"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

def take(n, iterable):
    """Take first n elements"""
    for i, item in enumerate(iterable):
        if i >= n:
            break
        yield item

def map_gen(func, iterable):
    """Lazy map operation"""
    for item in iterable:
        yield func(item)

# Functional operations using generators
fib_squares = map_gen(lambda x: x**2, take(10, generate_fibonacci()))
result = list(fib_squares)
print(f"First 10 Fibonacci squares: {result}")
```

```javascript !! js
// JavaScript higher-order functions and functional programming

// 1. Array built-in higher-order functions
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// map: apply function to each element
const squared = numbers.map(x => x ** 2);
console.log(`Squared: ${squared}`);

// filter: filter elements
const evens = numbers.filter(x => x % 2 === 0);
console.log(`Evens: ${evens}`);

// reduce: accumulation operation
const sumAll = numbers.reduce((acc, x) => acc + x, 0);
const productAll = numbers.reduce((acc, x) => acc * x, 1);
console.log(`Sum: ${sumAll}, Product: ${productAll}`);

// forEach: side-effect operation
console.log("Numbers:");
numbers.forEach((num, index) => {
    console.log(`${index}: ${num}`);
});

// 2. Custom higher-order functions
function applyTwice(func, value) {
    return func(func(value));
}

const addOne = x => x + 1;
const result1 = applyTwice(addOne, 5);  // ((5 + 1) + 1) = 7
console.log(`Apply twice: ${result1}`);

// 3. Function composition
const compose = (f, g) => x => f(g(x));

const multiplyBy2 = x => x * 2;
const subtract1 = x => x - 1;

// Compose functions: multiply by 2 first, then subtract 1
const composed = compose(subtract1, multiplyBy2);
const result2 = composed(5);  // (5 * 2) - 1 = 9
console.log(`Composed function: ${result2}`);

// 4. Multi-function composition (pipeline)
const pipe = (...functions) => x => functions.reduce((acc, func) => func(acc), x);

// Create processing pipeline
const process = pipe(
    x => x * 2,      // multiply by 2
    x => x + 10,     // add 10
    x => x ** 2      // square
);

const result3 = process(3);  // ((3 * 2) + 10) ** 2 = 16 ** 2 = 256
console.log(`Pipeline result: ${result3}`);

// 5. Decorator pattern higher-order functions
function logCalls(func) {
    return function(...args) {
        console.log(`Calling ${func.name} with args: ${args}`);
        const result = func.apply(this, args);
        console.log(`${func.name} returned: ${result}`);
        return result;
    };
}

function retry(maxAttempts = 3) {
    return function(func) {
        return function(...args) {
            for (let attempt = 0; attempt < maxAttempts; attempt++) {
                try {
                    return func.apply(this, args);
                } catch (error) {
                    if (attempt === maxAttempts - 1) {
                        throw error;
                    }
                    console.log(`Attempt ${attempt + 1} failed: ${error.message}`);
                }
            }
        };
    };
}

// Apply decorators
const riskyOperation = retry(2)(logCalls(function riskyOp(x, y) {
    if (x < 0) {
        throw new Error("x cannot be negative");
    }
    return x / y;
}));

try {
    const result = riskyOperation(10, 2);
    console.log(`Risky operation result: ${result}`);
} catch (error) {
    console.log(`Operation failed: ${error.message}`);
}

// 6. Currying implementation
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            };
        }
    };
}

const addThree = (a, b, c) => a + b + c;
const curriedAddThree = curry(addThree);

// Using curried function
const add12 = curriedAddThree(1, 2);
const result4 = add12(3);  // 6
console.log(`Curried function: ${result4}`);

// Auto-currying syntax sugar
const curriedMultiply = a => b => c => a * b * c;
const result5 = curriedMultiply(2)(3)(4);  // 24
console.log(`Auto-curried: ${result5}`);

// 7. Partial function application
function partial(fn, ...partialArgs) {
    return function(...remainingArgs) {
        return fn(...partialArgs, ...remainingArgs);
    };
}

const multiply = (x, y, z) => x * y * z;

// Create partial function
const double = partial(multiply, 2);      // Fix first parameter to 2
const result6 = double(3, 4);             // 2 * 3 * 4 = 24
console.log(`Partial function: ${result6}`);

// 8. Functional data processing
function processData(data) {
    return data
        .filter(x => x > 0)           // filter positive numbers
        .map(x => x * 2)              // multiply by 2
        .filter(x => x % 4 === 0);    // filter divisible by 4
}

const testData = [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8];
const processed = processData(testData);
console.log(`Processed data: ${processed}`);

// 9. Functional programming utility functions
const FP = {
    // Lazy evaluation
    lazy(fn) {
        let cached = false;
        let result;
        
        return function() {
            if (!cached) {
                result = fn.apply(this, arguments);
                cached = true;
            }
            return result;
        };
    },
    
    // Memoization
    memoize(fn) {
        const cache = new Map();
        
        return function(...args) {
            const key = JSON.stringify(args);
            
            if (cache.has(key)) {
                return cache.get(key);
            }
            
            const result = fn.apply(this, args);
            cache.set(key, result);
            return result;
        };
    },
    
    // Debounce
    debounce(fn, delay) {
        let timeoutId;
        
        return function(...args) {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => fn.apply(this, args), delay);
        };
    },
    
    // Throttle
    throttle(fn, limit) {
        let inThrottle;
        
        return function(...args) {
            if (!inThrottle) {
                fn.apply(this, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }
};

// Test utility functions
const expensiveCalculation = FP.memoize(function(n) {
    console.log(`Computing fibonacci(${n})`);
    if (n < 2) return n;
    return expensiveCalculation(n - 1) + expensiveCalculation(n - 2);
});

console.log(`Fibonacci(10): ${expensiveCalculation(10)}`);
console.log(`Fibonacci(10) again: ${expensiveCalculation(10)}`);  // From cache

// 10. Functional data structures
class FunctionalArray {
    constructor(items = []) {
        this.items = [...items];
    }
    
    map(fn) {
        return new FunctionalArray(this.items.map(fn));
    }
    
    filter(fn) {
        return new FunctionalArray(this.items.filter(fn));
    }
    
    reduce(fn, initial) {
        return this.items.reduce(fn, initial);
    }
    
    flatMap(fn) {
        return new FunctionalArray(this.items.flatMap(fn));
    }
    
    take(n) {
        return new FunctionalArray(this.items.slice(0, n));
    }
    
    drop(n) {
        return new FunctionalArray(this.items.slice(n));
    }
    
    toArray() {
        return [...this.items];
    }
    
    // Support method chaining
    pipe(...functions) {
        return functions.reduce((acc, func) => func(acc), this);
    }
}

// Using functional array
const functionalResult = new FunctionalArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    .filter(x => x % 2 === 0)     // [2, 4, 6, 8, 10]
    .map(x => x * x)              // [4, 16, 36, 64, 100]
    .take(3)                      // [4, 16, 36]
    .toArray();

console.log(`Functional array result: ${functionalResult}`);

// 11. Async higher-order functions
async function asyncMap(array, asyncFn) {
    const promises = array.map(asyncFn);
    return Promise.all(promises);
}

async function asyncFilter(array, asyncPredicate) {
    const results = await asyncMap(array, asyncPredicate);
    return array.filter((_, index) => results[index]);
}

// Simulate async operations
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

async function isEvenAsync(num) {
    await delay(10);  // Simulate async operation
    return num % 2 === 0;
}

async function doubleAsync(num) {
    await delay(10);  // Simulate async operation
    return num * 2;
}

// Using async higher-order functions
(async () => {
    const nums = [1, 2, 3, 4, 5];
    
    const evens = await asyncFilter(nums, isEvenAsync);
    console.log(`Async filtered evens: ${evens}`);
    
    const doubled = await asyncMap(nums, doubleAsync);
    console.log(`Async mapped doubled: ${doubled}`);
})();
```
</PythonEditor>

## Summary

This module provided an in-depth comparison of function mechanisms in Python and JavaScript:

### Key Differences:

1. **Function Definition**: JavaScript has multiple definition methods (declaration, expression, arrow functions), Python primarily uses `def` and `lambda`
2. **Scope Rules**: JavaScript has function scope and block scope, Python has LEGB rules
3. **Variable Hoisting**: JavaScript has hoisting behavior, Python doesn't
4. **Parameter Handling**: JavaScript uses destructuring and rest parameters, Python uses `*args` and `**kwargs`
5. **Closure Behavior**: Both support closures but have different pitfalls with loop variable capture

### Key Concepts:

1. **this Binding**: JavaScript's unique dynamic this binding mechanism
2. **Arrow Functions**: JavaScript ES6+ simplified syntax and lexical this
3. **Higher-Order Functions**: Both languages support functional programming patterns
4. **Performance Optimization**: Techniques like memoization, tail recursion optimization, and batching

Understanding these function mechanism differences is crucial for effectively transitioning from Python to JavaScript development. The next module will explore asynchronous programming, a core feature of JavaScript.
